package de.zainodis.commons.model.binding;

import java.lang.reflect.Method;

import de.zainodis.commons.LogCat;
import de.zainodis.commons.exception.ReflectionException;
import de.zainodis.commons.utils.ReflectionUtils;

/**
 * A datapath is a path in a tree (a connected graph without cycles). Each
 * datapath leads from the root down to a property. An example for a datapath:
 * Owner/Name - the root object from which we start traversing along the
 * datapath is "Car" (Omitted because it is the context object from which the
 * traversal starts). Car has a property named "Owner" and "Owner" has a
 * property named "Name". Properties are accessed via their getter/setter. The
 * datapath nodes are hence the names of the getter and setter (rather than the
 * property name itself) to which the prefix set - or get is prepended in order
 * to call the desired accessor.
 * 
 * Once constructed, a {@link DataSource} can be modified, without requiring the
 * presence of its original context root object (that object must however still
 * exist).
 * 
 * A {@link DataSource} could for instance be bound to dialog fields in order to
 * quickly edit the DatapathObject's referenced property value.
 * 
 * Changes to a datasource affect the linked object - likewise, changes to the
 * linked object affect the values in the datasource.
 * 
 * @author zainodis
 * 
 */
public class DataSource implements IDataSource {

   private static final String TAG = "DataSource";

   // The original datapath from which this Datasource was created
   private IDatapath path;
   // Required to actually modify the properties value
   private Object parent;
   // Setter and getter methods to modify or retrieve the properties value
   private Method setter;
   private Method getter;

   /**
    * Creates a new {@link DataSource} that is referencing a property of a class
    * instance.
    * 
    * 
    * @param parent
    *           parent object - required to modify the referenced property
    * @param getter
    *           method to access the getter of the referenced property
    * @param setter
    *           method to access the setter of the referenced property
    */
   DataSource(Object parent, Method getter, Method setter) {
	 this.parent = parent;
	 this.setter = setter;
	 this.getter = getter;
   }

   /**
    * Sets the value for this {@link DataSource}. The DatapathObject is
    * referencing a property of the original root object used to create it,
    * modification on this DatapathObject will consequently cause the same
    * modifications on the original root objects property.
    * 
    * @param value
    *           new value of the property which this DatapathObject is
    *           referencing. The argument type must either be of the same type
    *           as the property, or of a subtype.
    * 
    * @throws IllegalArgumentException
    *            thrown, if the requirements for a valid value are not fulfilled
    */
   @Override
   public final void setValue(Object value) {
	 if (setter != null) {
	    invokeAccessor(setter, value);
	 } else {
	    // This litters the logfile if set to warning/error/info
	    LogCat.d(TAG, "There is no equivalent setter for " + getter.getName());
	 }
   }

   /**
    * Returns the current value of the property, which this {@link DataSource}
    * is referencing.
    * 
    * @return the value wrapped as an object
    * @throws DatapathException
    *            if one of the provided arguments was invalid, exceptions thrown
    *            by subsequent processing are wrapped and re-thrown as a
    *            DatapathException
    */
   @Override
   public final Object getValue() {
	 return invokeAccessor(getter);
   }

   @Override
   public IDatapath getPath() {
	 return path;
   }

   public void setPath(IDatapath path) {
	 this.path = path;
   }

   private Object invokeAccessor(Method accessor, Object... arguments) {
	 try {
	    return ReflectionUtils.invoke(accessor, parent, arguments);

	 } catch (IllegalArgumentException e) {
	    String errorMessage = "Invalid argument for accessor call, cause: " + e.getMessage();
	    LogCat.e(TAG, errorMessage);
	    throw new DatapathException(errorMessage, e);
	 } catch (ReflectionException e) {
	    String errorMessage = "Failed to invoke " + getter.getName() + " on instance of "
			+ parent.getClass().getCanonicalName() + " cause: " + e.getMessage();
	    LogCat.e(TAG, errorMessage);
	    throw new DatapathException(errorMessage, e);
	 }

   }
}